/*****************************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 ****************************************************************************/

package org.apache.padaf.xmpbox.parser;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.SimpleTimeZone;

/**
 * This class is used to convert dates to strings and back using the PDF date
 * standards. Date are described in PDFReference1.4 section 3.8.2
 * 
 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
 * @author <a href="mailto:chris@oezbek.net">Christopher Oezbek</a>
 * 
 * @version $Revision: 1.3 $
 */
public class DateConverter {

	// The Date format is supposed to be the PDF_DATE_FORMAT, but not all PDF
	// documents
	// will use that date, so I have added a couple other potential formats
	// to try if the original one does not work.
	private static final SimpleDateFormat[] POTENTIAL_FORMATS = new SimpleDateFormat[] {
		new SimpleDateFormat("EEEE, dd MMM yyyy hh:mm:ss a"),
		new SimpleDateFormat("EEEE, MMM dd, yyyy hh:mm:ss a"),
		new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"),
		new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz") };

	/**
	 * According to check-style, Utility classes should not have a public or
	 * default constructor.
	 */
	protected DateConverter() {
	};

	/**
	 * This will convert a string to a calendar.
	 * 
	 * @param date
	 *            The string representation of the calendar.
	 * 
	 * @return The calendar that this string represents.
	 * 
	 * @throws IOException
	 *             If the date string is not in the correct format.
	 */
	public static Calendar toCalendar(String date) throws IOException {
		Calendar retval = null;
		if ((date != null) && (date.trim().length() > 0)) {
			// these are the default values
			int year = 0;
			int month = 1;
			int day = 1;
			int hour = 0;
			int minute = 0;
			int second = 0;
			// first string off the prefix if it exists
			try {
				SimpleTimeZone zone = null;
				if (date.startsWith("D:")) {
					date = date.substring(2, date.length());
				}

				date = date.replaceAll("[-:T]", "");

				if (date.length() < 4) {
					throw new IOException("Error: Invalid date format '" + date
							+ "'");
				}
				year = Integer.parseInt(date.substring(0, 4));
				if (date.length() >= 6) {
					month = Integer.parseInt(date.substring(4, 6));
				}
				if (date.length() >= 8) {
					day = Integer.parseInt(date.substring(6, 8));
				}
				if (date.length() >= 10) {
					hour = Integer.parseInt(date.substring(8, 10));
				}
				if (date.length() >= 12) {
					minute = Integer.parseInt(date.substring(10, 12));
				}

				int timeZonePos = 12;
				if (date.length() - 12 > 5 ||(date.length() - 12 == 3 && date.endsWith("Z"))) {
					if (date.length() >= 14) {
						second = Integer.parseInt(date.substring(12, 14));
					}
					timeZonePos = 14;
				} else {
					second = 0;
				}

				if (date.length() >= (timeZonePos+1)) {
					char sign = date.charAt(timeZonePos);
					if (sign == 'Z') {
						zone = new SimpleTimeZone(0, "Unknown");
					} else {
						int hours = 0;
						int minutes = 0;
						if (date.length() >= (timeZonePos+3)) {
							if (sign == '+') {
								// parseInt cannot handle the + sign
								hours = Integer.parseInt(date.substring((timeZonePos+1), (timeZonePos+3)));
							} else {
								hours = -Integer.parseInt(date.substring(timeZonePos, (timeZonePos+2)));
							}
						}
						if (sign == '+') {
							if (date.length() >= (timeZonePos+5)) {
								minutes = Integer.parseInt(date.substring((timeZonePos+3), (timeZonePos+5)));
							}
						} else {
							if (date.length() >= (timeZonePos+4)) {
								minutes = Integer.parseInt(date.substring((timeZonePos+2), (timeZonePos+4)));
							}
						}
						zone = new SimpleTimeZone(hours * 60 * 60 * 1000
								+ minutes * 60 * 1000, "Unknown");
					}
				}


				if (zone == null) {
					retval = new GregorianCalendar();
				} else {
					retval = new GregorianCalendar(zone);
				}
				retval.clear();
				retval.set(year, month - 1, day, hour, minute, second);
			} catch (NumberFormatException e) {

				// remove the arbitrary : in the timezone. SimpleDateFormat
				// can't handle it
				if (date.substring(date.length() - 3, date.length() - 2)
						.equals(":")
						&& (date
								.substring(date.length() - 6, date.length() - 5)
								.equals("+") || date.substring(
										date.length() - 6, date.length() - 5).equals(
												"-"))) {
					// thats a timezone string, remove the :
					date = date.substring(0, date.length() - 3)
							+ date.substring(date.length() - 2);
				}
				for (int i = 0; (retval == null)
						&& (i < POTENTIAL_FORMATS.length); i++) {
					try {
						Date utilDate = POTENTIAL_FORMATS[i].parse(date);
						retval = new GregorianCalendar();
						retval.setTime(utilDate);
					} catch (ParseException pe) {
						// ignore and move to next potential format
					}
				}
				if (retval == null) {
					// we didn't find a valid date format so throw an exception
					throw new IOException("Error converting date:" + date);
				}
			}
		}
		return retval;
	}

	/**
	 * Append Zero to String Buffer before number < 10 ('1' become '01')
	 * 
	 * @param out
	 *            The String buffer
	 * @param number
	 *            The concerned number
	 */
	private static void zeroAppend(StringBuffer out, int number) {
		if (number < 10) {
			out.append("0");
		}
		out.append(number);
	}

	/**
	 * Convert the date to iso 8601 string format.
	 * 
	 * @param cal
	 *            The date to convert.
	 * @return The date represented as an ISO 8601 string.
	 */
	public static String toISO8601(Calendar cal) {
		StringBuffer retval = new StringBuffer();

		retval.append(cal.get(Calendar.YEAR));
		retval.append("-");
		zeroAppend(retval, cal.get(Calendar.MONTH) + 1);
		retval.append("-");
		zeroAppend(retval, cal.get(Calendar.DAY_OF_MONTH));
		retval.append("T");
		zeroAppend(retval, cal.get(Calendar.HOUR_OF_DAY));
		retval.append(":");
		zeroAppend(retval, cal.get(Calendar.MINUTE));
		retval.append(":");
		zeroAppend(retval, cal.get(Calendar.SECOND));

		int timeZone = cal.get(Calendar.ZONE_OFFSET)
				+ cal.get(Calendar.DST_OFFSET);
		if (timeZone < 0) {
			retval.append("-");
		} else {
			retval.append("+");
		}
		timeZone = Math.abs(timeZone);
		// milliseconds/1000 = seconds = seconds / 60 = minutes = minutes/60 =
		// hours
		int hours = timeZone / 1000 / 60 / 60;
		int minutes = (timeZone - (hours * 1000 * 60 * 60)) / 1000 / 1000;
		if (hours < 10) {
			retval.append("0");
		}
		retval.append(Integer.toString(hours));
		retval.append(":");
		if (minutes < 10) {
			retval.append("0");
		}
		retval.append(Integer.toString(minutes));
		return retval.toString();
	}
}
